
#ifndef	ZIG_X_EXCEPTION_HPP
#define	ZIG_X_EXCEPTION_HPP

#include	<stdexcept>
#include	<ostream>
#include	<string>

#include	"zig/string.hpp"

#ifndef	__FUNC__
#ifdef	__GNUC__
#define	__FUNC__	__PRETTY_FUNCTION__
#else
#define	__FUNC__	__func__
#endif
#endif	// __FUNC__

namespace	zig
{

	class	Exception	:	public std::runtime_error
	{

	public	:
		typedef	std::runtime_error	Parent;

		Exception(const std::string& msg, 
					int error=0, int line=0, const char* file=NULL, const char* func=NULL)
			:	Parent(msg),
				m_error(error), m_line(line), m_file(file), m_func(func),
				m_prev(NULL)
		{
		}

		Exception(const std::string& msg, const Exception& ex, 
					int error=0, int line=0, const char* file=NULL, const char* func=NULL)
			:	Parent(msg),
				m_error(error), m_line(line), m_file(file), m_func(func),
				m_prev(new Exception(ex))
		{
		}

		Exception(const Exception& ex) 
			:	Parent(ex),
				m_error(ex.m_error), m_line(ex.m_line), m_file(ex.m_file), m_func(ex.m_func)
		{
			m_prev = ex.m_prev ? new Exception(*ex.m_prev) : NULL;
		}

		~Exception() throw ()
		{
			if (NULL != m_prev) {
				delete m_prev;
			}	
		}

	public	:
		int	code() const { return m_error; }
		int	line() const { return m_line; }
		const char*	file() const { return m_file; }
		const char*	func() const { return m_func; }

		Exception*	prev() const { return m_prev; }

	private	:
		const Exception&	operator=(const Exception&);

	private	:
		int		m_error;
		int		m_line;
		const char*	m_file;
		const char*	m_func;

		Exception*	m_prev;
	};	// class Exception


	#define	THROW(_code, _msg) \
		throw Exception(_msg, _code, __LINE__, __FILE__, __FUNC__)

	#define	THROW_FMT(_code, _fmt, _args...) \
		throw Exception(String::format(_fmt, ##_args), _code, __LINE__, __FILE__, __FUNC__)

	#define	THROW_EX(_code, _ex, _msg) \
		throw Exception(_msg, _ex, _code, __LINE__, __FILE__, __FUNC__)

	#define	THROW_EX_FMT(_code, _ex, _fmt, _args...) \
		throw Exception(String::format(_fmt, ##_args), _ex, _code, __LINE__, __FILE__, __FUNC__)

	#define	EXTERNAL	0xFFFF

	#define TRY(_s_, _code_) do { \
		try { _code_; } \
		catch (Exception &e) { \
			THROW_EX(e.code(), e, _s_); \
		} \
		catch (std::bad_alloc &e) { \
			THROW(EXTERNAL, "bad alloc " _s_); \
		} \
		catch (std::exception &e) { \
			THROW_FMT(EXTERNAL, "External exception " _s_ ": %s",  e.what()); \
		} \
	} while (0)


	std::ostream&	operator<<(std::ostream& out, const Exception& ex)
	{
		//out <<"Exception: " << "[" << ex.code() << "] " << ex.what();
		out <<"Exception: ";

		if (ex.line()) {
			//out <<"\n\tat "<< ex.func() <<" ("<< ex.file() <<':'<< ex.line() <<')';
			out <<"at "<< ex.func() <<" ("<< ex.file() <<':'<< ex.line() <<"): "
				<< "[" << ex.code() << "] " << ex.what();
		}
		else {
			out << "[" << ex.code() << "] " << ex.what();
		}

		for (Exception *prev = ex.prev(); prev; prev = prev->prev()) {
			if (prev->line()) {
				out <<"\n           at "<< (prev->func() ? prev->func() : "-") <<" ("
					<< (prev->file() ? prev->file() : "-") <<':'<< prev->line() <<"): "
					<< "[" << prev->code() << "] " 
					<< prev->what();
			}
			else {
				out <<"\n           " << "[" << prev->code() << "] " << prev->what();
			}
		}

		return out;
	}

}	// namespace zig


#endif	// ZIG_X_EXCEPTION_HPP

